﻿using System;
using System.IO;
using System.IO.Ports;
using System.Threading;

namespace Futaba.SBus.Readers
{
	/// <summary>
	/// Implements <see cref="ISbusStreamReader"/> and reads data from the serial-bus (COM Port)
	/// </summary>
  public class SerialSbusStreamReader : ISbusStreamReader
  {
    private SerialPort _port;
    private byte? _peekedByte;

		/// <summary>
		/// The default baud-rate for an S.BUS connection
		/// </summary>
		public const int DefaultPortRate = 98000;

		/// <summary>
		/// The name of the Port as it must be supplied to the <see cref="SerialPort"/> class
		/// </summary>
    public string PortId { get; set; }
		/// <summary>
		/// The baud-rate to connect to the port at.
		/// </summary>
		/// <seealso cref="DefaultPortRate"/>
    public int BaudRate { get; set; }

		/// <summary>
		/// An optional stream to log all received bytes to
		/// </summary>
    public Stream LoggingStream { get; set; }

		/// <summary>
		/// Creates a default instance
		/// </summary>
    public SerialSbusStreamReader()
    {
			BaudRate = DefaultPortRate;
    }

		/// <summary>
		/// Returns the next byte waiting in the stream's buffer, or NULL, if no data is available
		/// </summary>
		/// <returns></returns>
		public byte? ReadByte()
    {
      if (_peekedByte.HasValue)
      {
        var value = _peekedByte.Value;
        _peekedByte = null;
        return value;
      }
      try
      {
        //if (_port.BytesToRead == 0)
        //	return null;
        var value = GetNextByte();
        return value;
      }
      catch (Exception)
      {
        //port (USB) has been removed from system
        OpenPort();
        return null;
      }
    }

		/// <summary>
		/// Returns the next byte waiting in the stream's buffer but will not remove it from the 
		/// buffer, or NULL, if no data is available
		/// </summary>
		/// <remarks>
		/// Multiple calls will always return the same byte value (if a non-null value has been returned
		/// on previous calls), until <see cref="ReadByte"/> has been called
		/// </remarks>
		public byte? Peek()
    {
      try
      {
        //if (_port.BytesToRead == 0)
        //	return null;
        if (!_peekedByte.HasValue)
          _peekedByte = GetNextByte();
        return _peekedByte.Value;
      }
      catch (Exception)
      {
        //port (USB) has been removed from system
        OpenPort();
        return null;
      }
    }

		/// <summary>
		/// Works similar to <see cref="Peek"/>, but will consume the byte, if it matches the
		/// <paramref name="match">supplied value</paramref>
		/// </summary>
		/// <param name="match">the value to match against</param>
		/// <returns></returns>
		public bool ReadIfMatch(byte match)
    {
      var peek = Peek();
      if (!peek.HasValue || peek.Value != match)
        return false;

      ReadByte();
      return true;
    }

		/// <summary>
		/// Opens the underlying stream
		/// </summary>
		public virtual void OpenPort()
    {
      if (_port == null)
        _port = new SerialPort(PortId, BaudRate, Parity.Even, 8, StopBits.Two);
      _port.ReadTimeout = 500;

      if (!_port.IsOpen)
      {
        try
        {
          _port.Open();
        }
        catch (Exception)
        {
          Thread.Sleep(500);
        }
      }
    }

		/// <summary>
		/// Closes the underlying stream and releases all related resources
		/// </summary>
		public virtual void ReleasePort()
    {
      if (_port == null)
        return;

      try
      {
        if (_port.IsOpen)
          _port.Close();
      }
      // ReSharper disable EmptyGeneralCatchClause
      catch (Exception)
      // ReSharper restore EmptyGeneralCatchClause
      {
      }
      _port.Dispose();
      _port = null;
    }

    private byte GetNextByte()
    {
      var value = (byte)_port.ReadByte();
      if (LoggingStream != null)
        LoggingStream.WriteByte(value);
      return value;
    }
  }
}
